package edu.towson.cosc716.driver;

import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;


import edu.towson.cosc716.Commands.KitchenOrderPreparedCmd;
import edu.towson.cosc716.gui.SimulationMoniter;
import edu.towson.cosc716.interfaces.ObservableInterface;
import edu.towson.cosc716.interfaces.ObserverInterface;
import edu.towson.cosc716.models.Chef;
import edu.towson.cosc716.models.Customer;
import edu.towson.cosc716.models.InformationAggregator;
import edu.towson.cosc716.models.Invoker;
import edu.towson.cosc716.models.Offer;
import edu.towson.cosc716.models.Order;
import edu.towson.cosc716.models.SystemInterface;
import edu.towson.cosc716.models.MenuItem;
import edu.towson.cosc716.models.Table;


// TODO: Auto-generated Javadoc
/**
 * The Class Driver.
 */
public class Driver implements ObservableInterface {

	/** The ticker. */
	private final Timer ticker = new Timer();
	
	/** The information aggregator. */
	private InformationAggregator informationAggregator;
	
	/** The invoker. */
	private Invoker invoker;
	
	/** The system interface. */
	private SystemInterface systemInterface;
	
	// Simulation control variables.
	/** The customer entry rate limit. */
	private int customerEntryRateLimit = 5;
	
	/** The table occupancy time limit. */
	private int tableOccupancyTimeLimit = 12;
	
	/** The table occupancy party count. */
	private int tableOccupancyPartyCount = 4;
	
	/** The next customer. */
	private int nextCustomer = 0;
	// Key is tableId and Value is ticks remaining till they leave.  A value of -1 means unoccupied as of last check.
	/** The table ticker. */
	private HashMap<Integer, Integer> tableTicker = new HashMap<Integer, Integer>();
	
	/** The table ordered yet. */
	private HashMap<Integer, Boolean> tableOrderedYet = new HashMap<Integer, Boolean>();
	
	
	/** The count. */
	private int count = 0;

	/**
	 * Instantiates a new driver.
	 */
	public Driver() {

	}


	/**
	 * Start.
	 *
	 * @throws Exception the exception
	 */
	public void start() throws Exception {
		// Read in items to populate.
		int chefCount = 3;
		String[] chefNames = {"Smith", "Jones", "Gump"};
		HashMap<Integer, Integer> diningRoomSetup = new HashMap<Integer, Integer>();
		diningRoomSetup.put(4, 10);
		informationAggregator = new InformationAggregator(chefCount, chefNames, diningRoomSetup);

		Enumeration tables = informationAggregator.diningRoom.getAllTables();
		while (tables.hasMoreElements()) {
			Table table = (Table)tables.nextElement();
			tableTicker.put(table.getTableId(), -1);
			tableOrderedYet.put(table.getTableId(), false);
		}
		
		//  For testing only
		runTest(informationAggregator);
		

		SimulationMoniter sim = new SimulationMoniter(informationAggregator);
		sim.show();
		this.invoker = new Invoker(informationAggregator);
		invoker.addObserver(sim);
		for (Map.Entry<Chef, Order> entry : informationAggregator.kitchen.entrySet()) {
			entry.getKey().addObserver(invoker);
		}
		this.systemInterface = new SystemInterface(invoker);
		this.nextCustomer = randomNumberGenerator(customerEntryRateLimit);

		//  Simulation and GUI
		ticker.schedule(new TimerTask() {
			public void run() {
				

				if ( count == 600) {
					System.out.println("Avg Prep: " + informationAggregator.avgPrepTime.getAverage());
					System.out.println("Avg wait: " + informationAggregator.avgWaitTime.getAverage());
					System.out.println("Seating Queue: " + informationAggregator.seatingQueue.size());
				}
				//  This is where stuff occurs.
				
				//  Customer incoming stuff
				if (nextCustomer == 0) {
					systemInterface.requestTable("Max", randomNumberGenerator(tableOccupancyPartyCount));
					nextCustomer = randomNumberGenerator(customerEntryRateLimit);
				} else {
					nextCustomer--;
				}
				
				// Order stuff
				for (Map.Entry<Integer, Integer> entry : tableTicker.entrySet()) {
					if (entry.getValue() != -1 && !tableOrderedYet.get(entry.getKey())) {
						Iterator iterator = informationAggregator.menu.getMenu();
						List<Integer> menuItemIds = new ArrayList<Integer>();
						int i = 0;
						while(iterator.hasNext()) {
							MenuItem menuItem = (MenuItem)iterator.next();
							if (menuItem.getInventory() > 0) {
								menuItemIds.add(menuItem.getItemId());
								i++;
							}
						}
						for (i = 0; i < informationAggregator.diningRoom.find(entry.getKey()).getTableOccupancy(); i++ ) {
							if (tableOccupancyPartyCount == 2) {
								systemInterface.placeOrder(1, entry.getKey());
							}
							int menuItemId = randomNumberGenerator(menuItemIds.size() + 1);
							systemInterface.placeOrder(menuItemIds.get(menuItemId - 1), entry.getKey());	
						}
						tableOrderedYet.put(entry.getKey(), true);
					}
				}
				
				// Chef Stuff
				for (Map.Entry<Chef, Order> entry : informationAggregator.kitchen.entrySet()) {
					if (entry.getValue() == null) {
						systemInterface.nextKitchenItem(entry.getKey().getChefId());
					} else {
						if (entry.getValue().getCookTime() == 0) {
							systemInterface.kitchenItemPrepared(entry.getKey().getChefId());
						}
					}
				}
				
				//  Table occupancy and clear stuff
				for (Map.Entry<Integer, Integer> entry : tableTicker.entrySet()) {
					//  Handle newly occupied table
					if (informationAggregator.diningRoom.find(entry.getKey()).isOccupied() && entry.getValue() == -1) {
						entry.setValue(randomNumberGenerator(tableOccupancyTimeLimit) * informationAggregator.diningRoom.find(entry.getKey()).getTableOccupancy());
					// Handle customer leaving
					} else if (entry.getValue() == 0) {
						// replace with call to generate and pay tab.
						List<Order> orders = informationAggregator.preparedOrders.getOrdersForTable(entry.getKey());
						for (Order order : orders) {
							if (randomNumberGenerator(3) == 1) {
								systemInterface.reviewMenuItem(order.getMenuItemId(), randomNumberGenerator(6));
							}
						}
						systemInterface.payTab(entry.getKey());
						
						systemInterface.clearTable(entry.getKey());
						tableOrderedYet.put(entry.getKey(), false);
						entry.setValue(-1);
					//  Change to remove the preparedOrders part of && for demo if order behavior not completed yet.
					} else if (entry.getValue() > 0 && informationAggregator.diningRoom.find(entry.getKey()).getTableOccupancy() == informationAggregator.preparedOrders.orderCountByTable(entry.getKey())) {
						entry.setValue(entry.getValue() - 1);
					}
				}
				systemInterface.tick();
				count++;
			} // End of the run method.

		}, 1000, 1000);
	}


	/**
	 * Run test.
	 *
	 * @param informationAggregator the information aggregator
	 * @throws Exception the exception
	 */
	public void runTest(InformationAggregator informationAggregator) throws Exception {
		List<MenuItem> menuItems = generateMenuItems();
		for (MenuItem menuItem : menuItems) {
			informationAggregator.menu.addMenuItem(menuItem);
		}
		List<Offer> offers = new ArrayList<Offer>();
		Offer offer = new Offer(20, 10);
		offers.add(offer);
		informationAggregator.offers.add(offer);
		
	}
	
	/**
	 * Generate menu items.
	 *
	 * @return the list
	 */
	private List<MenuItem> generateMenuItems() {
		List<MenuItem> menuItems = new ArrayList<MenuItem>() ;
		// Ribeye Steak
		MenuItem menuItem = new MenuItem();
		menuItem.setName("Ribeye Steak");
		menuItem.setMiType("Entree");
		//menuItem.setItemId(2);
		menuItem.setCookTime(15);
		menuItem.setStartPrice(12.00);
		menuItem.setCurrentPrice(menuItem.getStartPrice());
		menuItem.setBasePrice(8.00);
		menuItems.add(menuItem);
		
		// Fried Chicken
		menuItem = new MenuItem();
		menuItem.setName("Fried Chicken");
		menuItem.setMiType("Entree");
		//menuItem.setItemId(1);
		menuItem.setCookTime(10);
		menuItem.setStartPrice(8.00);
		menuItem.setCurrentPrice(menuItem.getStartPrice());
		menuItem.setBasePrice(6.50);
		menuItems.add(menuItem);
		
		// Lobster
		menuItem = new MenuItem();
		menuItem.setName("Lobster");
		menuItem.setMiType("Entree");
		//menuItem.setItemId(3);
		menuItem.setCookTime(15);
		menuItem.setStartPrice(18.00);
		menuItem.setCurrentPrice(menuItem.getStartPrice());
		menuItem.setBasePrice(14.00);
		menuItems.add(menuItem);
		
		//  Fries
		menuItem = new MenuItem();
		menuItem.setName("Fries");
		menuItem.setMiType("Starter");
		//menuItem.setItemId(4);
		menuItem.setCookTime(6);
		menuItem.setStartPrice(5.00);
		menuItem.setCurrentPrice(menuItem.getStartPrice());
		menuItem.setBasePrice(3.00);
		menuItems.add(menuItem);
		
		// Cheese Sticks
		menuItem = new MenuItem();
		menuItem.setName("Cheese Sticks");
		menuItem.setMiType("Starter");
		//menuItem.setItemId(5);
		menuItem.setCookTime(4);
		menuItem.setStartPrice(6.00);
		menuItem.setCurrentPrice(menuItem.getStartPrice());
		menuItem.setBasePrice(3.50);
		menuItems.add(menuItem);
		
		// Milkshake
		menuItem = new MenuItem();
		menuItem.setName("Milkshake");
		menuItem.setMiType("Dessert");
		//menuItem.setItemId(6);
		menuItem.setCookTime(5);
		menuItem.setStartPrice(7.00);
		menuItem.setCurrentPrice(menuItem.getStartPrice());
		menuItem.setBasePrice(5.00);
		menuItems.add(menuItem);
		
		for (MenuItem menuItemInventory : menuItems) {
			menuItemInventory.setInventory(100);
		}
		
		return menuItems;
	}
	
	/**
	 * Random number generator.
	 *
	 * @param limit the limit
	 * @return the int
	 */
	private int randomNumberGenerator(int limit) {
		Random randomNumberGenerator = new Random();
		int nextInt =  randomNumberGenerator.nextInt(limit);
		if (nextInt == 0) {
			nextInt = 1;
		}
		return nextInt;
		
	}
	
	/**
	 * The main method.
	 *
	 * @param args the arguments
	 * @throws Exception the exception
	 */
	public static void main(String[] args) throws Exception {

		Driver driver = new Driver();
		driver.start();
	}


	/* (non-Javadoc)
	 * @see edu.towson.cosc716.interfaces.ObservableInterface#addObserver(edu.towson.cosc716.interfaces.ObserverInterface)
	 */
	@Override
	public void addObserver(ObserverInterface o) {
		// TODO Auto-generated method stub
		
	}


	/* (non-Javadoc)
	 * @see edu.towson.cosc716.interfaces.ObservableInterface#removeObserver(edu.towson.cosc716.interfaces.ObserverInterface)
	 */
	@Override
	public void removeObserver(ObserverInterface o) {
		// TODO Auto-generated method stub
		
	}


	/* (non-Javadoc)
	 * @see edu.towson.cosc716.interfaces.ObservableInterface#getType()
	 */
	@Override
	public String getType() {
		// TODO Auto-generated method stub
		return null;
	}
}
